package spring.data.jpa.config;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource;
import org.springframework.data.repository.config.DefaultRepositoryConfiguration;
import org.springframework.data.repository.config.RepositoryConfiguration;
import org.springframework.data.repository.config.RepositoryConfigurationDelegate;
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
import org.springframework.data.repository.config.RepositoryConfigurationSource;
import org.springframework.data.repository.config.XmlRepositoryConfigurationSource;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

public class SimpleRepositoryConfigurationDelegate extends RepositoryConfigurationDelegate {

    private final RepositoryConfigurationSource configurationSource;
    private final ResourceLoader resourceLoader;
    private final boolean isXml;

    public SimpleRepositoryConfigurationDelegate(RepositoryConfigurationSource configurationSource,
            ResourceLoader resourceLoader, Environment environment) {
        super(configurationSource, resourceLoader, environment);

        this.isXml = configurationSource instanceof XmlRepositoryConfigurationSource;
        boolean isAnnotation = configurationSource instanceof AnnotationRepositoryConfigurationSource;

        Assert.isTrue(isXml || isAnnotation,
                "Configuration source must either be an Xml- or an AnnotationBasedConfigurationSource!");
        Assert.notNull(resourceLoader, "ResourceLoader must not be null!");

        this.configurationSource = configurationSource;
        this.resourceLoader = resourceLoader;
    }

    @Override
    public List<BeanComponentDefinition> registerRepositoriesIn(BeanDefinitionRegistry registry,
            RepositoryConfigurationExtension extension) {
        extension.registerBeansForRoot(registry, configurationSource);

        SimpleRepositoryBeanDefinitionBuilder builder = new SimpleRepositoryBeanDefinitionBuilder();
        List<BeanComponentDefinition> definitions = new ArrayList<>();

        for (BeanDefinition candidate : configurationSource.getCandidates(resourceLoader)) {
            RepositoryConfiguration<? extends RepositoryConfigurationSource> configuration = new DefaultRepositoryConfiguration<RepositoryConfigurationSource>(
                    configurationSource, candidate, extension);

            BeanDefinitionBuilder definitionBuilder = builder.build(configuration, extension);

            extension.postProcess(definitionBuilder, configurationSource);

            if (isXml) {
                extension.postProcess(definitionBuilder, (XmlRepositoryConfigurationSource) configurationSource);
            } else {
                extension.postProcess(definitionBuilder, (AnnotationRepositoryConfigurationSource) configurationSource);
            }

            RootBeanDefinition beanDefinition = (RootBeanDefinition) definitionBuilder.getBeanDefinition();

            // /////////////////////////////////////////////////
            String shortClassName = StringUtils.uncapitalize(ClassUtils.getShortName(configuration
                    .getRepositoryInterface()));
            String repositoryName = shortClassName + "Repository";
            if (!ArrayUtils.contains(registry.getBeanDefinitionNames(), repositoryName)) {
                registry.registerBeanDefinition(repositoryName, beanDefinition);
                definitions.add(new BeanComponentDefinition(beanDefinition, repositoryName));
            }
        }

        return definitions;
    }
}
